/*
 * Copyright 2024 The CHANGLEI Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

part of '../px.dart';

/// 像素大小
extension RectToPx on Rect {
  /// 像素大小
  PxRect get px => PxRect.fromLTRB(left.px, top.px, right.px, bottom.px);

  /// 像素大小
  PxRect toPx(BuildContext context) {
    return PxRect.fromLTRB(
      left.toPx(context),
      top.toPx(context),
      right.toPx(context),
      bottom.toPx(context),
    );
  }
}

/// Created by changlei on 2024/4/23.
///
/// See [Rect]
class PxRect implements _Px<Rect> {
  /// See [Rect.fromLTRB]
  const PxRect.fromLTRB(this.left, this.top, this.right, this.bottom);

  /// See [Rect.fromLTWH]
  const PxRect.fromLTWH(PxDouble left, PxDouble top, PxDouble width, PxDouble height)
      : this.fromLTRB(left, top, left + width, top + height);

  /// See [Rect.fromCircle]
  PxRect.fromCircle({required PxOffset center, required PxDouble radius})
      : this.fromCenter(
          center: center,
          width: radius * 2.px,
          height: radius * 2.px,
        );

  /// See [Rect.fromCenter]
  PxRect.fromCenter({required PxOffset center, required PxDouble width, required PxDouble height})
      : this.fromLTRB(
          center.dx - width / 2.px,
          center.dy - height / 2.px,
          center.dx + width / 2.px,
          center.dy + height / 2.px,
        );

  /// See [Rect.fromPoints]
  PxRect.fromPoints(PxOffset a, PxOffset b)
      : this.fromLTRB(
          min(a.dx, b.dx),
          min(a.dy, b.dy),
          max(a.dx, b.dx),
          max(a.dy, b.dy),
        );

  /// [Rect._getValue32]
  Float32List getValue32() {
    final Float32List result = Float32List(4);
    result[0] = left._;
    result[1] = top._;
    result[2] = right._;
    result[3] = bottom._;
    return result;
  }

  /// See [Rect.left]
  final PxDouble left;

  /// See [Rect.top]
  final PxDouble top;

  /// See [Rect.right]
  final PxDouble right;

  /// See [Rect.bottom]
  final PxDouble bottom;

  /// See [Rect.width]
  PxDouble get width => right - left;

  /// See [Rect.height]
  PxDouble get height => bottom - top;

  /// See [Rect.size]
  PxSize get size => PxSize(width, height);

  /// See [Rect.hasNaN]
  bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN;

  /// See [Rect.zero]
  static const PxRect zero = PxRect.fromLTRB(PxDouble.zero, PxDouble.zero, PxDouble.zero, PxDouble.zero);

  static const double _giantScalar = 1000000000; // matches kGiantRect from layer.h

  /// See [Rect.largest]
  static const PxRect largest = PxRect.fromLTRB(
    PxDouble(-_giantScalar),
    PxDouble(-_giantScalar),
    PxDouble(_giantScalar),
    PxDouble(_giantScalar),
  );

  /// See [Rect.isInfinite]
  bool get isInfinite {
    return left >= PxDouble.infinity ||
        top >= PxDouble.infinity ||
        right >= PxDouble.infinity ||
        bottom >= PxDouble.infinity;
  }

  /// See [Rect.isFinite]
  bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;

  /// See [Rect.isEmpty]
  bool get isEmpty => left >= right || top >= bottom;

  /// See [Rect.shift]
  PxRect shift(PxOffset offset) {
    return PxRect.fromLTRB(left + offset.dx, top + offset.dy, right + offset.dx, bottom + offset.dy);
  }

  /// See [Rect.translate]
  PxRect translate(PxDouble translateX, PxDouble translateY) {
    return PxRect.fromLTRB(left + translateX, top + translateY, right + translateX, bottom + translateY);
  }

  /// See [Rect.inflate]
  PxRect inflate(PxDouble delta) {
    return PxRect.fromLTRB(left - delta, top - delta, right + delta, bottom + delta);
  }

  /// See [Rect.deflate]
  PxRect deflate(PxDouble delta) => inflate(-delta);

  /// See [Rect.intersect]
  PxRect intersect(PxRect other) {
    return PxRect.fromLTRB(
      max(left, other.left),
      max(top, other.top),
      min(right, other.right),
      min(bottom, other.bottom),
    );
  }

  /// See [Rect.expandToInclude]
  PxRect expandToInclude(PxRect other) {
    return PxRect.fromLTRB(
      min(left, other.left),
      min(top, other.top),
      max(right, other.right),
      max(bottom, other.bottom),
    );
  }

  /// See [Rect.overlaps]
  bool overlaps(PxRect other) {
    if (right <= other.left || other.right <= left) {
      return false;
    }
    if (bottom <= other.top || other.bottom <= top) {
      return false;
    }
    return true;
  }

  /// See [Rect.shortestSide]
  PxDouble get shortestSide => min(width.abs(), height.abs());

  /// See [Rect.longestSide]
  PxDouble get longestSide => max(width.abs(), height.abs());

  /// See [Rect.topLeft]
  PxOffset get topLeft => PxOffset(left, top);

  /// See [Rect.topCenter]
  PxOffset get topCenter => PxOffset(left + width / 2.0.px, top);

  /// See [Rect.topRight]
  PxOffset get topRight => PxOffset(right, top);

  /// See [Rect.centerLeft]
  PxOffset get centerLeft => PxOffset(left, top + height / 2.0.px);

  /// See [Rect.center]
  PxOffset get center => PxOffset(left + width / 2.0.px, top + height / 2.0.px);

  /// See [Rect.centerRight]
  PxOffset get centerRight => PxOffset(right, top + height / 2.0.px);

  /// See [Rect.bottomLeft]
  PxOffset get bottomLeft => PxOffset(left, bottom);

  /// See [Rect.bottomCenter]
  PxOffset get bottomCenter => PxOffset(left + width / 2.0.px, bottom);

  /// See [Rect.bottomRight]
  PxOffset get bottomRight => PxOffset(right, bottom);

  /// See [Rect.contains]
  bool contains(PxOffset offset) {
    return offset.dx >= left && offset.dx < right && offset.dy >= top && offset.dy < bottom;
  }

  /// See [Rect.lerp]
  static PxRect? lerp(PxRect? a, PxRect? b, double t) {
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        final k = (1.0 - t).px;
        return PxRect.fromLTRB(
          a.left * k,
          a.top * k,
          a.right * k,
          a.bottom * k,
        );
      }
    } else {
      if (a == null) {
        return PxRect.fromLTRB(
          b.left * t.px,
          b.top * t.px,
          b.right * t.px,
          b.bottom * t.px,
        );
      } else {
        return PxRect.fromLTRB(
          _lerpPxDouble(a.left, b.left, t),
          _lerpPxDouble(a.top, b.top, t),
          _lerpPxDouble(a.right, b.right, t),
          _lerpPxDouble(a.bottom, b.bottom, t),
        );
      }
    }
  }

  @override
  Rect toDp(BuildContext context) {
    return Rect.fromLTRB(
      left.toDp(context),
      top.toDp(context),
      right.toDp(context),
      bottom.toDp(context),
    );
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (runtimeType != other.runtimeType) {
      return false;
    }
    return other is PxRect && other.left == left && other.top == top && other.right == right && other.bottom == bottom;
  }

  @override
  int get hashCode => Object.hash(left, top, right, bottom);

  @override
  String toString() =>
      'PxRect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})';
}
